﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class BoardDisplay : MonoBehaviour, IBoardDisplay
{
	private GameObject[,] m_Board = null;
	private EffectsManager m_effectsManager = null;
	private const int m_constMinRowCount = 3;
	private float m_PositionX = 0.0f;
	private float m_PositionY = 0.0f;
	private float m_Width = 0.0f;
	private float m_Height = 0.0f;
	private bool m_UpdateDimensions = true;
	private AnimationHelper m_AnimationHelper = new AnimationHelper();
	private float m_AnimationSpeed = 0.3f;
    private GameManager m_gameManager;

	public XMLGems m_XMLGems { get; set; }

	public SpriteManager spriteManager { get; set; }
	// Board position and dimensions, change to any of these will trigger delayed 
	// adjustment on Update()
	public float positionX
	{
		get { return m_PositionX; }
		set
		{
			m_PositionX = value;
			m_UpdateDimensions = true;
		}
	}

	public float positionY
	{
		get { return m_PositionY; }
		set
		{
			m_PositionY = value;
			m_UpdateDimensions = true;
		}
	}

	public float width
	{
		get { return m_Width; }
		set
		{
			m_Width = value;
			m_UpdateDimensions = true;
		}
	}

	public float height
	{
		get { return m_Height; }
		set
		{
			m_Height = value;
			m_UpdateDimensions = true;
		}
	}
	// Cell dimensions dinamycly calculated based on the board dimensions
	public float cellWidth { get { return m_Board == null ? 0 : width / m_Board.GetLength(0); } }

	public float cellHeight { get { return m_Board == null ? 0 : height / m_Board.GetLength(1); } }
	// true if no animation is being played and thus next action can be done
	public bool isReady
	{
		get
		{
			return m_AnimationHelper.isIdle;
		}
	}

	public bool isMainQueueEmpty { get { return m_ActionQueue.Count == 0; } }
	// If queued, call UpdateTransform
	bool processedOnLastFrame = false;

	void Update()
	{
		if (m_UpdateDimensions && m_Board != null)
		{
			UpdateTransform();
			m_UpdateDimensions = false;
		}
	}

	private IEnumerator MainActionChainPump()
	{
		while (true)
		{
			if (m_ActionQueue.Count != 0)
			{
				ProcessAction(m_ActionQueue.Dequeue(), m_AnimationHelper);
				while (!isReady)
					yield return new WaitForEndOfFrame();
			}
			yield return new WaitForEndOfFrame();
		}
	}

	private void ParallelActionChainPump(Queue<Board.Action> actionChain)
	{
		StartCoroutine(ParallelActionChainRoutine(actionChain));
	}

	private IEnumerator ParallelActionChainRoutine(Queue<Board.Action> actionChain)
	{
		AnimationHelper animationHelper = new AnimationHelper();
		foreach (var action in actionChain)
		{
			ProcessAction(action, animationHelper);
			while (!animationHelper.isIdle)
				yield return new WaitForEndOfFrame();
		}
	}

	private void ProcessAction(Board.Action action, AnimationHelper animationHelper)
	{
		//var sound = GameObject.Find("Camera").GetComponent<Sound>();

		switch (action.type)
		{
			case Board.Action.Type.Add:
                //sound.PlayWhistle();//

				for (int i = 0; i < action.from.Count; i++)
				{
					Add(action.from[i].x, action.from[i].y, action.gemTypes[i], animationHelper);
				}
				break;
			case Board.Action.Type.Remove:
                //sound.PlayExplosion();
                
				for (int i = 0; i < action.lines.Count; i++)
				{
                    List<Point> currentLine = action.lines[i];
                    Point topLeft = currentLine[0];
                    Point bottomRight = currentLine[currentLine.Count - 1];
                    Vector3 worldPos = Vector3.Lerp(BoardToWorld(topLeft.x, topLeft.y, Vector2.zero, 1.5f), BoardToWorld(bottomRight.x, bottomRight.y, Vector2.one, 1.5f), 0.5f);
					int score = ScoreCount.CountScore(new ScoreCount.Line(action.playerID, action.lines[i].Count, action.gemTypes[i]));
                    Color color = Data.multiplayer ? m_gameManager.mainPlayer.GetPlayerInfo(action.playerID).color : m_XMLGems.GetColor(action.gemTypes[i]);
                    m_effectsManager.CreateFloatingText(worldPos, score.ToString(), color, 1.0f, 0.04f);
					foreach (var point in currentLine)
					{
						Remove(point.x, point.y, animationHelper, action.playerID, action.gemTypes[i]);
					}
				}
				break;
			case Board.Action.Type.Swap:
				for (int i = 0; i < action.from.Count; i++)
				{
					MakeSwap(action.from[i].x, action.from[i].y, action.to[i].x, action.to[i].y, animationHelper, action.playerID);
				}
				break;
			case Board.Action.Type.ComboBegin:
				ScoreCount.ComboBegin();
				break;
			case Board.Action.Type.ComboEnd:
				ScoreCount.ComboEnd();
				break;
		}
	}

	public void Bind(Board board)
	{
		if (board == null)
		{
			Debug.LogException(new System.Exception("Board is null!"));
		}
		board.onMainActionChain += new Board.ActionChainDelegate(OnMainActionChain);
		board.onNewActionChain += new Board.ActionChainDelegate(ParallelActionChainPump);
	}

	private Queue<Board.Action> m_ActionQueue = new Queue<Board.Action>();

	public void OnMainActionChain(Queue<Board.Action> actionQueue)
	{
		foreach (var action in actionQueue)
		{
			m_ActionQueue.Enqueue(action);
		}
	}

	public void PreInit(int width, int height, GameManager gameManager)
	{
		if (width < m_constMinRowCount || height < m_constMinRowCount)
			Debug.LogException(new System.Exception("Dimensions are too small!"));
		m_Board = new GameObject[height, width];
		StartCoroutine(MainActionChainPump());
		m_effectsManager = gameManager.mainEffectsManager;
        m_gameManager = gameManager;
	}

	public Rect GetRect()
	{
		return new Rect(positionX, positionY, width, height);
	}

	public int GetRowsCount()
	{
		return m_Board == null ? 0 : m_Board.GetLength(0);
	}

	public Vector3 GetPosition(int x, int y)
	{
		return BoardToScreen(x, y, new Vector2(0.5f, 0.5f));
	}

    protected Rect GetSize(int x, int y, Vector2 padding)
    {
		Vector3 bottomLeft = BoardToScreen(x, y, new Vector2(padding.x, padding.y));
		Vector3 topRight = BoardToScreen(x, y, new Vector2(1.0f - padding.x, 1.0f - padding.y));
		Rect targetDimensions = new Rect();
		targetDimensions.x = bottomLeft.x;
		targetDimensions.y = bottomLeft.y;
		targetDimensions.width = topRight.x - bottomLeft.x;
		targetDimensions.height = topRight.y - bottomLeft.y;

		return targetDimensions;

    }

	public Rect GetSize(int x, int y)
	{
        return GetSize(x, y, Vector2.zero);
	}

	// Manually update position and size of the board and all of it's elements, rarely needed though, as it's called automaticly when board dimensions change
	public void UpdateTransform()
	{
		if (m_Board == null)
			return;

		for (int y = 0; y < m_Board.GetLength(0); y++)
		{
			for (int x = 0; x < m_Board.GetLength(1); x++)
			{
				GameObject gem = m_Board[y, x];
				if (gem == null)
					continue;
				Transform gemTransform = gem.transform;
				PositionElement(gemTransform, x, y);
				ScaleElement(gem, x, y);
			}
		}
	}
	// <EVENTS> //
	public void SetSelection(bool selected, int x = 0, int y = 0)
	{
	}

	private void Add(int x, int y, int type, AnimationHelper animationHelper)
	{
		if (m_Board == null || ArrayTools2D.IsOutOfBounds(m_Board, x, y))
			return;

		if (m_Board[y, x] != null)
			Remove(x, y, animationHelper);

		GameObject newElement = new GameObject();
		string spriteName = m_XMLGems.spriteName(type);
		newElement.name = "Gem: " + spriteName;

		UISprite sprite = newElement.AddComponent<UISprite>();
		sprite.atlas = spriteManager.atlas;
		sprite.spriteName = spriteName;

		AddElement(newElement, x, y);

		Transform newTransform = m_Board[y, x].transform;
		PositionElement(newTransform, x, y);
		ScaleElement(m_Board[y, x], x, y);
		StartCoroutine(animationHelper.ShrinkGrow(newTransform, m_AnimationSpeed, false));
	}

	private void Remove(int x, int y, AnimationHelper animationHelper, int playerID = 0, int gemType = 0)
	{
		if (m_Board == null || ArrayTools2D.IsOutOfBounds(m_Board, x, y))
		{
			Debug.LogWarning(string.Format("Tried to delete x: {0} y: {1} and board is {2}", x, y, m_Board == null ? "null." : "not null"));
			return;
		}

		if (m_Board[y, x] != null)
		{
			GameObject entity = m_Board[y, x];

			var particlePosition = new Vector3(entity.transform.localPosition.x, entity.transform.localPosition.y, entity.transform.localPosition.z - 0.1f);
			m_effectsManager.CreateExplosionEffect(particlePosition, playerID, gemType);
			//var obj = GameObject.Instantiate(Resources.Load("gemExplosion"), particlePosition, entity.transform.rotation) as GameObject;
			//obj.transform.localScale = new Vector3(1.0f, 1.0f, 1.0f);
			//var part = obj.GetComponent<ParticleSystem>();
			//part.startColor = new Color(0, 0, 1.0f);

			//DestroyObject(obj, 2.0f);

			m_Board[y, x] = null;
			StartCoroutine(RemoveCoroutine(entity, m_AnimationSpeed, animationHelper));
		}
	}

	private IEnumerator RemoveCoroutine(GameObject entity, float animationTime, AnimationHelper animationHelper)
	{
		yield return StartCoroutine(animationHelper.ShrinkGrow(entity.transform, animationTime));
		Destroy(entity);
	}

	private void MakeSwap(int x1, int y1, int x2, int y2, AnimationHelper animationHelper, int playerID = 0)
	{
		if (m_Board == null || ArrayTools2D.IsOutOfBounds(m_Board, x1, y1) || ArrayTools2D.IsOutOfBounds(m_Board, x2, y2) || (x1 == x2 && y1 == y2))
			return;

		GameObject temp = m_Board[y1, x1];
		m_Board[y1, x1] = m_Board[y2, x2];
		m_Board[y2, x2] = temp;

		if (m_Board[y1, x1] != null && m_Board[y2, x2] != null)
		{
			UISprite sprite1 = m_Board[y1, x1].GetComponent<UISprite>();
			if (sprite1 != null)
				sprite1.depth = -1;

			StartCoroutine(animationHelper.CircularMove(m_Board[y1, x1].transform, BoardToWorld(x1, y1, new Vector2(0.5f, 0.5f)), m_AnimationSpeed, true));
			UISprite sprite2 = m_Board[y2, x2].GetComponent<UISprite>();
			if (sprite2 != null)
				sprite2.depth = 1;

			StartCoroutine(animationHelper.CircularMove(m_Board[y2, x2].transform, BoardToWorld(x2, y2, new Vector2(0.5f, 0.5f)), m_AnimationSpeed, false));
		}
		else if (m_Board[y1, x1] != null)
			StartCoroutine(animationHelper.SpringMove(m_Board[y1, x1].transform, BoardToWorld(x1, y1, new Vector2(0.5f, 0.5f)), m_AnimationSpeed, 6.0f));
		else if (m_Board[y2, x2] != null)
			StartCoroutine(animationHelper.SpringMove(m_Board[y2, x2].transform, BoardToWorld(x2, y2, new Vector2(0.5f, 0.5f)), m_AnimationSpeed, 6.0f));
	}
	// <\EVENTS> //
	// Add new element to specified position (if it's ocupied - not gonna happen), position it, scale it, make it good to go.
	private bool AddElement(GameObject element, int x, int y)
	{
		if (m_Board[y, x] != null)
			return false;

		m_Board[y, x] = element;
		Transform elementTransform = element.transform;
		elementTransform.parent = gameObject.transform;
		elementTransform.localRotation = Quaternion.identity;
		return true;
	}
	// Position element in the board grid, all things considered
	private void PositionElement(Transform element, int x, int y)
	{
		Vector3 screenPosition = BoardToScreen(x, y, new Vector2(0.5f, 0.5f));
		element.position = ScreenToWorld.ScreenPointToWorld(Mathf.RoundToInt(screenPosition.x), Mathf.RoundToInt(screenPosition.y));
	}
	// Scale element accordig to the grid dimensions
	private void ScaleElement(GameObject element, int x, int y)
	{
		Rect targetDimensions = GetSize(x, y, new Vector2(0.05f, 0.05f));

		ScreenToWorld.ScaleNGUISprite(element.GetComponent<UISprite>(), targetDimensions);
	}

	private Vector3 BoardToScreen(int x, int y, Vector2 align)
	{
		float screenX = positionX + cellWidth * x + cellWidth * align.x;

		float screenY = positionY + height - (cellHeight * y + cellHeight * align.y);
		return new Vector3(screenX, screenY, 0.0f);
	}

	private Vector3 BoardToWorld(int x, int y, Vector2 align)
	{
		Vector3 screenPoint = BoardToScreen(x, y, align);
		return ScreenToWorld.ScreenPointToWorld(Mathf.RoundToInt(screenPoint.x), Mathf.RoundToInt(screenPoint.y));
	}

    private Vector3 BoardToWorld(int x, int y, Vector2 align, float distanceFromCamera)
    {
        Vector3 screenPoint = BoardToScreen(x, y, align);
        return ScreenToWorld.ScreenPointToWorld(Mathf.RoundToInt(screenPoint.x), Mathf.RoundToInt(screenPoint.y), distanceFromCamera);
    }
}